﻿/*
 * Copyright (C) 2012, Jesus Requejo <jesus.requejo@gmail.com> 
 * Licensed under Microsoft Public License (Ms-PL)
 * http://www.microsoft.com/en-us/openness/licenses.aspx#MPL
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.ServiceProcess;
using System.Threading;
using Infrastructure;

namespace WindowsService {

   public partial class LoaderService : ServiceBase {

      IList<uint> m_sessionIDs;

      public LoaderService() {
         InitializeComponent();
      }

      protected override void OnStart(string[] args) {
         try {
            m_sessionIDs = new List<uint>();
            ThreadPool.QueueUserWorkItem((o) => { ListenForRequests(); });
         }
         catch (Exception) {
            Stop();
            // TODO: log the exception                
         }

      }

      private void ListenForRequests() {
         string ip = Infrastructure.ServiceMessenger.IP;
         IPAddress ipAddress = IPAddress.Parse(ip);
         int port = Infrastructure.ServiceMessenger.PORT;
         IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
         Socket serverSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
         serverSocket.Bind(localEndPoint);
         serverSocket.Listen(10);

         while (true) {
            Socket clientSocket = serverSocket.Accept();
            Infrastructure.ServiceMessenger.ReadRequestFromClientAndSendResponse(clientSocket, ProcessRequest);
         }
      }

      private string ProcessRequest(string request) {

         if (request.IndexOf(MagicStrings.StartupApplicationsLaunchedRequestMessage) >= 0) {
            return ProcessApplicationsAlreadyLaunchedRequest(request);
         }

         if (request.IndexOf(MagicStrings.StartupLaunchApplicationsRequestMessage) >= 0) {
            return ProcessLaunchApplicationsRequest(request);
         }

         return "UNKNOWN";
      }

      private string ProcessApplicationsAlreadyLaunchedRequest(string request) {
         uint requestedSessionId = GetSessionIdFromRequest(request);
         if (this.ApplicationsAlreadyLaunchedInCurrentSession(requestedSessionId)) {
            return MagicStrings.StartupApplicationsLaunchedAffirmativeResponseMessage;
         }
         return MagicStrings.StartupApplicationsLaunchedNegativeResponseMessage;
      }

      private string ProcessLaunchApplicationsRequest(string request) {
         uint requestedSessionId = GetSessionIdFromRequest(request);
         string userName = GetUserNameFromRequest(request);
         bool launchedSuccessfully;
         bool userIsAuthorized = PermissionsChecker.StartupApplicationsAuthorized(userName);
         if (userIsAuthorized) {
            try {
               LaunchApplications(userName, requestedSessionId);
               launchedSuccessfully = true;
            }
            catch (Exception) {
               launchedSuccessfully = false;
            }
            var success = MagicStrings.StartupLaunchApplicationsSuccessResponseMessage;
            var failure = MagicStrings.StartupLaunchApplicationsFailureResponseMessage;
            string result = launchedSuccessfully ? success : failure;
            return result;
         }
         else {
            var notAuthorized = MagicStrings.StartupLaunchApplicationsNotAuthorizedResponseMessage;
            return notAuthorized;
         }
      }

      private void LaunchAdminManager(string userName) {
         var path = RegistryManager.ReadInstallationPath();
         var executable = System.IO.Path.Combine(path, MagicStrings.ServiceManagerExecutable);
         var command = executable + " " + MagicStrings.StartMinimizedCommandArgument;
         LaunchProcess(command);
      }

      private uint GetSessionIdFromRequest(string request) {
         var parts = request.Split('_');
         uint requestedSessionId = uint.Parse(parts[1]);
         return requestedSessionId;
      }

      private string GetUserNameFromRequest(string request) {
         var parts = request.Split('_');
         string userName = parts[2];
         return userName;
      }

      void LogToFile(uint sessionId) {
         var path = Environment.GetFolderPath(System.Environment.SpecialFolder.CommonDocuments);
         path = Path.Combine(path, "logon_log.txt");
         var line = "logon at " + sessionId + ", " + DateTime.Now.ToString();
         LogToFileHelper(path, line);
      }

      void LogToFileHelper(string path, string message) {
         using (var sw = new StreamWriter(new FileStream(path, FileMode.Append))) {
            sw.WriteLine(message);
         }
      }

      private void LaunchApplications(string userName, uint sessionId) {
         var applications = GetApplicationsList().ToList();
         if (!ApplicationsAlreadyLaunchedInCurrentSession(sessionId)) {
            try {
               if (PermissionsChecker.LaunchManagerAuthorized(userName)) {
                  LaunchAdminManager(userName);
               }
            }
            catch (Exception) {
               // errors launching the service manager automatically are simply ignored
            }
            LaunchApplicationsHelper(applications);
            m_sessionIDs.Add(sessionId);
         }
      }

      private void LaunchApplicationsHelper(IEnumerable<string> applications) {
         foreach (var applicationName in applications) {
            LaunchProcess(applicationName);
         }
      }

      private bool ApplicationsAlreadyLaunchedInCurrentSession(uint currentSessionId) {
         if (m_sessionIDs.Contains(currentSessionId)) {
            return true;
         }
         return false;
      }

      private IEnumerable<string> GetApplicationsList() {
         // storage could be injected using a DI container to avoid a direct dependency on the file system
         // I didn't think it'd be worth it at this point
         IStorageStrategy storage = new Infrastructure.FileSystemStorage();
         var appCollection = storage.LoadApplicationsList();
         return appCollection
            .Where(app => app.LaunchAtLogon == true && app.Enabled == true)
            .Select(app => app.Path);
      }

      private static void LaunchProcess(string command) {
          ThirdParty.ApplicationLoader.PROCESS_INFORMATION procInfo;
          ThirdParty.ApplicationLoader.StartProcessAndBypassUAC(command, out procInfo);
      }

      protected override void OnStop() {

      }
   }
}